/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <iomanip>
#include <sstream>

#include "algorithm"
#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h"
#include "plugins/ecmascript/runtime/ecma_string-inl.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/js_array.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "plugins/ecmascript/runtime/js_invoker.h"
#include "plugins/ecmascript/runtime/js_object-inl.h"
#include "plugins/ecmascript/runtime/js_primitive_ref.h"
#include "plugins/ecmascript/runtime/js_tagged_value-inl.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/tests/runtime/common/test_helper.h"

// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript;
// NOLINTNEXTLINE(google-build-using-namespace)
using namespace ark::ecmascript::builtins;

// NOLINTBEGIN(readability-magic-numbers)

namespace ark::test {
class BuiltinsJsonTest : public testing::Test {
public:
    static void SetUpTestCase()
    {
        GTEST_LOG_(INFO) << "SetUpTestCase";
    }

    static void TearDownTestCase()
    {
        GTEST_LOG_(INFO) << "TearDownCase";
    }

    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

    class TestClass {
    public:
        static JSTaggedValue TestForParse(EcmaRuntimeCallInfo *argv)
        {
            uint32_t argc = argv->GetArgsNumber();
            if (argc > 0) {
            }
            JSTaggedValue key = builtins_common::GetCallArg(argv, 0).GetTaggedValue();
            if (key.IsUndefined()) {
                return JSTaggedValue::Undefined();
            }
            JSTaggedValue value = builtins_common::GetCallArg(argv, 1).GetTaggedValue();
            if (value.IsUndefined()) {
                return JSTaggedValue::Undefined();
            }

            return JSTaggedValue(value);
        }

        static JSTaggedValue TestForParse1(EcmaRuntimeCallInfo *argv)
        {
            uint32_t argc = argv->GetArgsNumber();
            if (argc > 0) {
            }
            return JSTaggedValue::Undefined();
        }

        static JSTaggedValue TestForStringfy(EcmaRuntimeCallInfo *argv)
        {
            uint32_t argc = argv->GetArgsNumber();
            if (argc > 0) {
                JSTaggedValue key = builtins_common::GetCallArg(argv, 0).GetTaggedValue();
                if (key.IsUndefined()) {
                    return JSTaggedValue::Undefined();
                }
                JSTaggedValue value = builtins_common::GetCallArg(argv, 1).GetTaggedValue();
                if (value.IsUndefined()) {
                    return JSTaggedValue::Undefined();
                }
                return JSTaggedValue(value);
            }

            return JSTaggedValue::Undefined();
        }
    };

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {nullptr};

private:
    PandaVM *instance_ {nullptr};
    EcmaHandleScope *scope_ {nullptr};
};

JSTaggedValue CreateBuiltinJSObject1(JSThread *thread, const PandaString &keyCStr)
{
    EcmaVM *ecmaVm = thread->GetEcmaVM();
    JSHandle<GlobalEnv> globalEnv = ecmaVm->GetGlobalEnv();
    ObjectFactory *factory = ecmaVm->GetFactory();
    JSHandle<JSTaggedValue> objectFunc(globalEnv->GetObjectFunction());

    JSHandle<JSObject> jsobject(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objectFunc), objectFunc));
    EXPECT_TRUE(*jsobject != nullptr);

    JSHandle<JSTaggedValue> key(factory->NewFromCanBeCompressString(&keyCStr[0]));
    JSHandle<JSTaggedValue> value(thread, JSTaggedValue(1));
    JSObject::SetProperty(thread, JSHandle<JSTaggedValue>(jsobject), key, value);

    PandaString str2 = "y";
    JSHandle<JSTaggedValue> key2(factory->NewFromCanBeCompressString(str2));
    JSHandle<JSTaggedValue> value2(thread, JSTaggedValue(2.5));  // 2.5 : test case
    JSObject::SetProperty(thread, JSHandle<JSTaggedValue>(jsobject), key2, value2);

    PandaString str3 = "z";
    JSHandle<JSTaggedValue> key3(factory->NewFromCanBeCompressString(str3));
    JSHandle<JSTaggedValue> value3(factory->NewFromCanBeCompressString("abc"));
    JSObject::SetProperty(thread, JSHandle<JSTaggedValue>(jsobject), key3, value3);

    return jsobject.GetTaggedValue();
}
// Math.abs(-10)

TEST_F(BuiltinsJsonTest, Parse10)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();

    JSHandle<JSTaggedValue> msg(factory->NewFromCanBeCompressString(
        "\t\r \n{\t\r \n \"property\"\t\r \n:\t\r \n{\t\r \n}\t\r \n,\t\r \n \"prop2\"\t\r \n:\t\r \n [\t\r \ntrue\t\r "
        "\n,\t\r \nnull\t\r \n,123.456\t\r \n] \t\r \n}\t\r \n"));
    JSHandle<EcmaString> str(JSTaggedValue::ToString(thread_, msg));

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, str.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Parse(ecmaRuntimeCallInfo.get());
    ASSERT_TRUE(result.IsECMAObject());
}

TEST_F(BuiltinsJsonTest, Parse21)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();

    JSHandle<JSTaggedValue> msg(factory->NewFromCanBeCompressString("[100,2.5,\"abc\"]"));

    JSHandle<JSFunction> handleFunc = factory->NewJSFunction(env, reinterpret_cast<void *>(TestClass::TestForParse));
    JSHandle<EcmaString> str(JSTaggedValue::ToString(thread_, msg));

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 8);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, str.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(1, handleFunc.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Parse(ecmaRuntimeCallInfo.get());
    ASSERT_TRUE(result.IsECMAObject());
}

TEST_F(BuiltinsJsonTest, Parse)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> lengthKeyHandle = thread_->GlobalConstants()->GetHandledLengthString();

    JSHandle<JSTaggedValue> msg(factory->NewFromCanBeCompressString("[100,2.5,\"abc\"]"));
    JSHandle<EcmaString> str(JSTaggedValue::ToString(thread_, msg));

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, str.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Parse(ecmaRuntimeCallInfo.get());
    JSTaggedValue value(static_cast<JSTaggedType>(result.GetRawData()));
    ASSERT_TRUE(value.IsECMAObject());
    JSHandle<JSObject> valueHandle(thread_, value);
    JSHandle<JSTaggedValue> lenResult =
        JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(valueHandle), lengthKeyHandle).GetValue();
    uint32_t length = JSTaggedValue::ToLength(thread_, lenResult).ToUint32();
    EXPECT_EQ(length, 3);
}

TEST_F(BuiltinsJsonTest, Parse2)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> msg(factory->NewFromCanBeCompressString(R"({"epf":100,"key1":200})"));
    JSHandle<EcmaString> str(JSTaggedValue::ToString(thread_, msg));

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, str.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Parse(ecmaRuntimeCallInfo.get());
    JSTaggedValue value(static_cast<JSTaggedType>(result.GetRawData()));
    ASSERT_TRUE(value.IsECMAObject());
    JSHandle<JSObject> valueHandle(thread_, value);

    JSHandle<TaggedArray> nameList(JSObject::EnumerableOwnNames(thread_, valueHandle));
    JSHandle<JSArray> nameResult = JSArray::CreateArrayFromList(thread_, nameList);

    JSHandle<JSTaggedValue> handleKey(nameResult);
    JSHandle<JSTaggedValue> lengthKey(factory->NewFromCanBeCompressString("length"));
    JSHandle<JSTaggedValue> lenResult = JSObject::GetProperty(thread_, handleKey, lengthKey).GetValue();
    uint32_t length = JSTaggedValue::ToLength(thread_, lenResult).ToUint32();
    ASSERT_EQ(length, 2);
}

TEST_F(BuiltinsJsonTest, Stringify11)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSTaggedValue> obj = JSHandle<JSTaggedValue>(thread_, CreateBuiltinJSObject1(thread_, "x"));
    JSHandle<JSFunction> handleFunc = factory->NewJSFunction(env, reinterpret_cast<void *>(TestClass::TestForStringfy));

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 8);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, obj.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(1, handleFunc.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Stringify(ecmaRuntimeCallInfo.get());
    ASSERT_TRUE(result.IsString());
}

TEST_F(BuiltinsJsonTest, Stringify12)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> obj = JSHandle<JSTaggedValue>(thread_, CreateBuiltinJSObject1(thread_, "x"));
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSFunction> handleFunc = factory->NewJSFunction(env, reinterpret_cast<void *>(TestClass::TestForStringfy));

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 10);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, obj.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(1, handleFunc.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(2, JSTaggedValue(static_cast<int32_t>(10)));

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Stringify(ecmaRuntimeCallInfo.get());
    ASSERT_TRUE(result.IsString());
}

TEST_F(BuiltinsJsonTest, Stringify13)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> obj = JSHandle<JSTaggedValue>(thread_, CreateBuiltinJSObject1(thread_, "x"));
    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSFunction> handleFunc = factory->NewJSFunction(env, reinterpret_cast<void *>(TestClass::TestForStringfy));
    JSHandle<JSTaggedValue> msg(factory->NewFromCanBeCompressString("tttt"));
    JSHandle<EcmaString> str(JSTaggedValue::ToString(thread_, msg));

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 10);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, obj.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(1, handleFunc.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(2, str.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Stringify(ecmaRuntimeCallInfo.get());
    ASSERT_TRUE(result.IsString());
}

TEST_F(BuiltinsJsonTest, Stringify14)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<JSTaggedValue> obj = JSHandle<JSTaggedValue>(thread_, CreateBuiltinJSObject1(thread_, "x"));
    JSArray *arr = JSArray::Cast(JSArray::ArrayCreate(thread_, JSTaggedNumber(0)).GetTaggedValue().GetTaggedObject());

    JSHandle<JSObject> obj1(thread_, arr);
    JSHandle<JSTaggedValue> key0(thread_, JSTaggedValue(0));
    JSHandle<JSTaggedValue> value0(factory->NewFromCanBeCompressString("x"));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key0, value0);
    JSHandle<JSTaggedValue> key1(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> value1(factory->NewFromCanBeCompressString("z"));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key1, value1);

    JSHandle<JSTaggedValue> msg(factory->NewFromCanBeCompressString("tttt"));
    JSHandle<EcmaString> str(JSTaggedValue::ToString(thread_, msg));

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 10);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, obj.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(1, obj1.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(2, str.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Stringify(ecmaRuntimeCallInfo.get());
    ASSERT_TRUE(result.IsString());
}

TEST_F(BuiltinsJsonTest, Stringify)
{
    JSHandle<JSTaggedValue> obj = JSHandle<JSTaggedValue>(thread_, CreateBuiltinJSObject1(thread_, "x"));
    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, obj.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Stringify(ecmaRuntimeCallInfo.get());
    ASSERT_TRUE(result.IsString());
}

TEST_F(BuiltinsJsonTest, Stringify1)
{
    auto ecmaVm = thread_->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();
    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();

    JSArray *arr = JSArray::Cast(JSArray::ArrayCreate(thread_, JSTaggedNumber(0)).GetTaggedValue().GetTaggedObject());

    EXPECT_TRUE(arr != nullptr);
    JSHandle<JSObject> obj(thread_, arr);
    JSHandle<JSTaggedValue> key0(thread_, JSTaggedValue(0));

    JSHandle<JSTaggedValue> value(factory->NewFromCanBeCompressString("def"));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key0, value);

    JSHandle<JSTaggedValue> key1(thread_, JSTaggedValue(1));
    PropertyDescriptor desc1(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(200)), true, true, true);
    JSArray::DefineOwnProperty(thread_, obj, key1, desc1);

    JSHandle<JSTaggedValue> key2(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> value2(factory->NewFromCanBeCompressString("abc"));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key2, value2);

    JSHandle<JSFunction> handleFunc = factory->NewJSFunction(env, reinterpret_cast<void *>(TestClass::TestForStringfy));
    JSHandle<JSTaggedValue> msg(factory->NewFromCanBeCompressString("tttt"));
    JSHandle<EcmaString> str(JSTaggedValue::ToString(thread_, msg));

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 10);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, obj.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(1, handleFunc.GetTaggedValue());
    ecmaRuntimeCallInfo->SetCallArg(2, str.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Stringify(ecmaRuntimeCallInfo.get());
    ASSERT_TRUE(result.IsString());
}

TEST_F(BuiltinsJsonTest, Stringify2)
{
    auto ecmaVm = thread_->GetEcmaVM();
    ObjectFactory *factory = ecmaVm->GetFactory();

    JSArray *arr = JSArray::Cast(JSArray::ArrayCreate(thread_, JSTaggedNumber(0)).GetTaggedValue().GetTaggedObject());
    EXPECT_TRUE(arr != nullptr);
    JSHandle<JSObject> obj(thread_, arr);

    JSHandle<JSTaggedValue> key0(thread_, JSTaggedValue(0));
    PropertyDescriptor desc0(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(1)), true, true, true);
    JSArray::DefineOwnProperty(thread_, obj, key0, desc0);
    JSHandle<JSTaggedValue> key1(thread_, JSTaggedValue(1));
    // 2.5 : test case
    PropertyDescriptor desc1(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(2.5)), true, true, true);
    JSArray::DefineOwnProperty(thread_, obj, key1, desc1);
    // 2 : test case
    JSHandle<JSTaggedValue> key2(thread_, JSTaggedValue(2));
    JSHandle<JSTaggedValue> value2(factory->NewFromCanBeCompressString("abc"));
    JSObject::SetProperty(thread_, JSHandle<JSTaggedValue>(obj), key2, value2);

    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 6);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
    ecmaRuntimeCallInfo->SetCallArg(0, obj.GetTaggedValue());

    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    JSTaggedValue result = json::Stringify(ecmaRuntimeCallInfo.get());
    ASSERT_TRUE(result.IsString());
}
}  // namespace ark::test

// NOLINTEND(readability-magic-numbers)
